using CurrentGame.Utils.Interpolation;
using Godot;
using System;
using System.Collections.Generic;


public partial class CinematicCamera : Node2D
{
	[Signal]
	public delegate void CameraMaskAnimationFinishedEventHandler(bool currentShow);


    readonly struct ConfigCopy(
        Node2D focus = null, float zoom = 1.0f,
		Vector2 offset = default, bool enableLimit = false, Rect2I limit = default)
    {
        public readonly Node2D focus = focus;
        public readonly float zoom = zoom;
        public readonly bool enableLimit = enableLimit;
        public readonly Rect2I limit = limit;
		public readonly Vector2 offset = offset;
    }


    [Export]
	public Vector2I CameraRawSize { get; set; } = new Vector2I(1920, 1080);

	[Export]
	public Node2D InitialPosition { get; set; }

	[Export]
	public Node2D Focus { get; set; }
	private Vector2 _currentPosition, _targetPosition;

	[Export]
	public Vector2 FocusOffset { get; set; } = Vector2.Zero;


	// Zoom
	[Export(PropertyHint.Range, "0,10")]
	public float Zoom
	{
		get => _zoomFactor;
		set
		{
			if (IsNodeReady())
				SetTargetZoomLevel(value);
			else
			{
				UpdateCameraZoom(value);
				_targetZoomFactor = value;
			}
		}
	}
	private float _zoomFactor = 1.0f, _targetZoomFactor = 1.0f;
	private bool _smoothZooming = false;
	private Vector2 _currentCamSize;


	// Limit box
	[Export]
	public bool EnableLimitBox { get; set; } = false;
	[Export]
	public Rect2I LimitBox { get; set; }

    // Camera Mask
    [Export]
	public bool ShowMask
	{
		get => _showMask;
		set
		{
			if (_animationPlayer == null || _maskSprite == null) return;
			if (_showMask != value)
			{
				_showMask = value;
				if (value) ShowMaskSprite();
				else HideMaskSprite();
			}
		}
	}
	private bool _showMask = true;

    // Zoom smoothing
    [ExportGroup("Zoom Smoothing")]
	[Export]
	public bool SmoothZoom { get; set; } = false;
	[Export]
	public float SmoothZoomSpeed { get; set; } = 4.0f;


	// Position smoothing
	[ExportGroup("Position Smoothing")]
	[Export]
	public bool SmoothPosition { get; set; } = false;
	[Export(PropertyHint.Range, "0,1000")]
	public float SmoothPositionSpeed { get; set; } = 5.0f;


	// Configuration node
	private List<ConfigCopy> _acaStack;


	// Background interpolation
	private LerpInterpolation _bgInterpolation;


	// Child node references
	private Camera2D _camera;
	private AnimationPlayer _animationPlayer;
	private Sprite2D _maskSprite, _backgroundSprite;
	private List<Sprite2D> _overlaySprites;
	private List<float> _overlayInitialScale;

	public CinematicCamera()
	{
		_acaStack = [];
		_overlaySprites = [];
		_overlayInitialScale = [];
		LimitBox = new Rect2I();

		_bgInterpolation = new LerpInterpolation(0.016f, new Vector3(0.001f, -1f, -1f))
		{
			Weight = 0.02f
		};
	}


	// Godot callbacks

	// Called when the node enters the scene tree for the first time.
	public override void _Ready()
	{
		// Init positions
		if (InitialPosition == null)
			GlobalPosition = Vector2.Zero;
		else
			GlobalPosition = InitialPosition.GlobalPosition;
		_currentPosition = GlobalPosition;
		_targetPosition = GlobalPosition;
		ResetPhysicsInterpolation(); // teleport

		// Smooth initial camera zoom
		_currentCamSize = (Vector2)CameraRawSize / _zoomFactor;

		// Set default config object
		_acaStack.Add(new ConfigCopy(Focus, Zoom, FocusOffset, EnableLimitBox, LimitBox));

		// Get child node references
		_camera = GetNode<Camera2D>("Camera");
		_animationPlayer = GetNode<AnimationPlayer>("Mask/AnimationPlayer");
		_backgroundSprite = GetNode<Sprite2D>("Background");
		_maskSprite = GetNode<Sprite2D>("Mask");
		_maskSprite.Visible = true;

		_bgInterpolation.Value = _backgroundSprite.Modulate.R;
		_bgInterpolation.Teleport();

        AddOverlaySprite(_maskSprite);
		AddOverlaySprite(_backgroundSprite);

		// Sync initial zoom
        _camera.Zoom = new Vector2(_zoomFactor, _zoomFactor);
		UpdateOverlayZoom(_zoomFactor);
    }

    public override void _Process(double delta)
    {
        if (_bgInterpolation.Process(delta))
		{
			var current = _bgInterpolation.Value;
			_backgroundSprite.Modulate = new Color(current, current, current);
		}
    }

    public override void _PhysicsProcess(double delta)
    {
		// Update zoom interpolation
		if (_smoothZooming)
		{
			if (Mathf.Abs(_targetZoomFactor - _zoomFactor) < 0.001f)
			{
				_zoomFactor = _targetZoomFactor;
				_smoothZooming = false;
			}
			else
			{
				UpdateCameraZoom(
					(float)Mathf.Lerp(_zoomFactor, _targetZoomFactor, (float)delta * SmoothZoomSpeed)
				);
			}
		}

		_targetPosition = Focus.GlobalPosition;
		_targetPosition += new Vector2(_currentCamSize.X * FocusOffset.X, _currentCamSize.Y * FocusOffset.Y);

        // Pass limit box
        if (EnableLimitBox)
        {
            Vector2I camSize = (Vector2I)_currentCamSize;
            Rect2I camCenterRect = new Rect2I(
                new Vector2I(
                    LimitBox.Position.X - LimitBox.Size.X / 2 + camSize.X / 2,
                    LimitBox.Position.Y - LimitBox.Size.Y / 2 + camSize.Y / 2),
                new Vector2I(
                    Mathf.Max(0, LimitBox.Size.X - camSize.X),
                    Mathf.Max(0, LimitBox.Size.Y - camSize.Y))
            );

            _targetPosition.X = Mathf.Clamp(_targetPosition.X, camCenterRect.Position.X, camCenterRect.End.X);
            _targetPosition.Y = Mathf.Clamp(_targetPosition.Y, camCenterRect.Position.Y, camCenterRect.End.Y);
        }

        // Update position interpolation
        if (SmoothPosition)
		{
            _currentPosition = _currentPosition.Lerp(_targetPosition, (float)delta * SmoothPositionSpeed);
		}
		else
		{
            _currentPosition = _targetPosition;
		}

		GlobalPosition = _currentPosition;
    }


	// Private functions

	private void SetTargetZoomLevel(float zoom)
	{
		if (SmoothZoom)
		{
			_targetZoomFactor = zoom;
			_smoothZooming = true;
		}
		else
		{
			UpdateCameraZoom(zoom);
			_targetZoomFactor = zoom;
		}
	}

	private void UpdateCameraZoom(float zoom)
	{
		if (zoom < 0.05) return;
		_currentCamSize = new Vector2(CameraRawSize.X / zoom, CameraRawSize.Y / zoom);
		_zoomFactor = zoom;

		// Apply zoom to camera
		if (_camera != null)
			_camera.Zoom = new Vector2(zoom, zoom);

		UpdateOverlayZoom(zoom);
	}

	private void UpdateOverlayZoom(float zoom)
	{
		var factor = 1.0f / zoom;
		for (int i = 0; i < _overlaySprites.Count; ++i)
		{
			var scale = factor * _overlayInitialScale[i];
			_overlaySprites[i].Scale = new Vector2(scale, scale);
		}
    }

	private void AddOverlaySprite(Sprite2D sprite)
	{
		var scale = (float)CameraRawSize.X / sprite.Texture.GetWidth();
		sprite.Scale = new Vector2(scale, scale);
		_overlaySprites.Add(sprite);
		_overlayInitialScale.Add(scale);
	}

	private void ApplyConfigCopy(ConfigCopy config)
	{
		Focus = config.focus;
		Zoom = config.zoom;
		EnableLimitBox = config.enableLimit;
		LimitBox = config.limit;
		FocusOffset = config.offset;
	}

    private void ShowMaskSprite()
    {
		_maskSprite.Visible = true;
		_animationPlayer.Play("leave");
    }

    private void HideMaskSprite()
    {
		_animationPlayer.Play("enter");
    }

	private void Signal_MaskAnimationFinished(StringName animName)
	{
		var animation = animName.ToString();
		if (animation == "enter")
		{
			_maskSprite.Visible = false;
		}
		EmitSignal(SignalName.CameraMaskAnimationFinished, animation == "leave");
	}


    // Exposed functions

    public void PushCameraAreaConfig(CameraAreaConfig config)
	{
		// Copy current config
		var limitBox = LimitBox;
		var focus = Focus;
		var zoom = Zoom;
		var enableLimit = EnableLimitBox;
		var offset = FocusOffset;

		// Change focus
		if (config.SetFocus) focus = config.FocusNode;
		// Change zoom
		if (config.SetZoom) zoom = config.Zoom;
		// Change Offset
		if (config.SetOffset) offset = config.Offset;
		// Change limit box
		if (config.SetLimit)
		{
			enableLimit = config.EnableLimitBox;

			limitBox = config.LimitBox;
			// Compute global space Rect from local space Rect in config
			limitBox.Position += (Vector2I)config.GlobalPosition;
		}

		var cc = new ConfigCopy(focus, zoom, offset, enableLimit, limitBox);
		_acaStack.Add(cc);
		ApplyConfigCopy(cc);
	}

	public void PushCameraAreaConfig(
		bool setFocus, bool setZoom, bool setLimit, bool setOffset,
		Node2D focus = null, float zoom = 1.0f, Vector2 offset = default,
		bool enableLimit = false, Rect2I limit = default)
	{
        // Copy current config
        var _imitBox = LimitBox;
        var _focus = Focus;
        var _zoom = Zoom;
        var _enableLimit = EnableLimitBox;
		var _offset = FocusOffset;

        // Change focus
        if (setFocus) _focus = focus;
        // Change zoom
        if (setZoom) _zoom = zoom;
		// change focus
		if (setOffset) _offset = offset;
        // Change limit box
        if (setLimit)
        {
            _enableLimit = enableLimit;
            _imitBox = limit;
        }

        var cc = new ConfigCopy(_focus, _zoom, _offset, _enableLimit, _imitBox);
        _acaStack.Add(cc);
        ApplyConfigCopy(cc);
    }

    public void PopCameraAreaConfig()
	{
		if (_acaStack.Count > 1)
		{
			_acaStack.RemoveAt(_acaStack.Count - 1);
			ApplyConfigCopy(_acaStack[^1]);
		}
	}

	public Vector2I GetCurrentCameraSize()
	{
		return new Vector2I(
			(int)(CameraRawSize.X / _targetZoomFactor),
			(int)(CameraRawSize.Y / _targetZoomFactor));
	}

	public void ResetInterpolation()
	{
		_currentPosition = _targetPosition;
		_zoomFactor = _targetZoomFactor;
		ResetPhysicsInterpolation();
	}

	public void SetBackgroundColor(float gray)
	{
		_bgInterpolation.Value = gray;
	}

}
